package com.ysy.serviceImpl;

import com.ysy.entity.NoteSearchReq;
import com.ysy.entity.NoteSearchResponse;
import com.ysy.entity.es.NoteIndex;
import com.ysy.enums.NotePublishTimeRangeEnum;
import com.ysy.enums.NoteSortTypeEnum;
import com.ysy.enums.ResponseCodeEnum;
import com.ysy.persimmons.common.constant.DateConstants;
import com.ysy.persimmons.common.response.PageResponse;
import com.ysy.persimmons.common.util.AssertUtil;
import com.ysy.persimmons.common.util.DateUtils;
import com.ysy.persimmons.common.util.NumberUtils;
import com.ysy.persimmons.common.util.StringUtils;
import com.ysy.service.NoteSearchService;
import jakarta.annotation.Resource;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FieldValueFactorFunction;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FieldValueFactorFunctionBuilder;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Service
public class NoteSearchServiceImpl implements NoteSearchService {

    private final static Logger log = LoggerFactory.getLogger(NoteSearchServiceImpl.class);

    @Resource
    private RestHighLevelClient restHighLevelClient;

    @Override
    public PageResponse<NoteSearchResponse> getNoteInfos(NoteSearchReq noteSearchReq) {

        String keyword = noteSearchReq.getKeyword();
        AssertUtil.isFalse(StringUtils.isBlank(keyword), ResponseCodeEnum.PARAM_NOT_VALID);
        Integer page = noteSearchReq.getPage();
        if (noteSearchReq.getPage() == null || noteSearchReq.getPage() < 1) {
            page=1;
        }
        int from = (page-1) * 10;
        // 构建请求搜索索引，数据库中的表
        SearchRequest searchRequest = new SearchRequest(NoteIndex.NAME);
        // 构建条件器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 权重和条件构建
        // bool 查询：bool 是一种组合查询，它可以结合多个查询条件。主要包含以下子句：
        //must：文档必须满足的条件，类似于 SQL 中的 AND。
        //should：文档满足这些条件会增加相关性分数，类似于 SQL 中的 OR。
        //filter：文档必须满足的条件，但不会影响相关性分数。
        //term 查询：用于精确匹配字段值。
        //must_not：文档必须不满足的条件，类似于 SQL 中的 NOT。
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.multiMatchQuery(keyword)
                .field(NoteIndex.FIELD_NOTE_TITLE, 2.0f) // 默认是1.0的权重
                .field(NoteIndex.FIELD_NOTE_TOPIC)
        );
        // 类型判断 null：不限 / 0：图文 / 1：视频
       if (Objects.nonNull(noteSearchReq.getType())) {
           // 文档必须满足的条件
           boolQueryBuilder.filter(QueryBuilders.termQuery(NoteIndex.FIELD_TYPE, noteSearchReq.getType()));
       }

        // 发布时间范围判断 null：不限 / 0：一天内 / 1：一周内 / 2：半年内 /其他不做处理
        NotePublishTimeRangeEnum notePublishTimeRangeEnum = NotePublishTimeRangeEnum.valueOf(noteSearchReq.getPublishTimeRange());
        if (notePublishTimeRangeEnum != null) {
            String startTime = null;
            switch (notePublishTimeRangeEnum) {
                case DAY-> startTime = DateUtils.localDateTimeString(LocalDateTime.now().minusDays(1));
                case WEEK -> startTime = DateUtils.localDateTimeString(LocalDateTime.now().minusWeeks(7));
                case HALF_YEAR -> startTime = DateUtils.localDateTimeString(LocalDateTime.now().minusDays(6));
            }
            if (startTime != null) {
                String endTime = LocalDateTime.now().format(DateConstants.DATE_FORMAT_Y_M_D_H_M_S);
                // 文档必须满足的条件
                boolQueryBuilder.filter(QueryBuilders.rangeQuery(NoteIndex.FIELD_NOTE_CREATE_TIME)
                        .gte(startTime)
                        .lte(endTime)
                );
            }
        }

        // 排序判断 null：不限 / 0：最新 / 1：最多点赞 / 2：最多评论 / 3：最多收藏
        NoteSortTypeEnum noteSortTypeEnum = NoteSortTypeEnum.valueOf(noteSearchReq.getSort());
        if (noteSortTypeEnum != null) {
            switch (noteSortTypeEnum) {
                case LATEST -> searchSourceBuilder.sort(new FieldSortBuilder(NoteIndex.FIELD_NOTE_CREATE_TIME).order(SortOrder.DESC));
                case MOST_LIKE -> searchSourceBuilder.sort(new FieldSortBuilder(NoteIndex.FIELD_NOTE_LIKE_TOTAL).order(SortOrder.DESC));
                case MOST_COMMENT -> searchSourceBuilder.sort(new FieldSortBuilder(NoteIndex.FIELD_NOTE_COMMENT_TOTAL).order(SortOrder.DESC));
                case MOST_COLLECT -> searchSourceBuilder.sort(new FieldSortBuilder(NoteIndex.FIELD_NOTE_COLLECT_TOTAL).order(SortOrder.DESC));
            }
            searchSourceBuilder.query(boolQueryBuilder);
        }else {
            // 默认为空自定义排序,按 _score（文档得分）降序排序，确保得分最高的文档排在最前。
            searchSourceBuilder.sort(new FieldSortBuilder("_score").order(SortOrder.DESC));
            FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{

                    // 最多点赞
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                            new FieldValueFactorFunctionBuilder(NoteIndex.FIELD_NOTE_LIKE_TOTAL)
                                    .factor(0.6f)
                                    .modifier(FieldValueFactorFunction.Modifier.SQRT)
                                    .missing(0)
                    ),
                    // 最多收藏
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                            new FieldValueFactorFunctionBuilder(NoteIndex.FIELD_NOTE_COLLECT_TOTAL)
                                    .factor(0.3f)
                                    .modifier(FieldValueFactorFunction.Modifier.SQRT)
                                    .missing(0)
                    ),
                    // 最多评论
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                            new FieldValueFactorFunctionBuilder(NoteIndex.FIELD_NOTE_COMMENT_TOTAL)
                                    .factor(0.2f)
                                    .modifier(FieldValueFactorFunction.Modifier.SQRT)
                                    .missing(0)
                    )
            };
            // score_mode : 定义函数得分如何与查询匹配得分结合，值 "sum" 表示将所有函数的得分相加。
            // boost_mode : 定义最终的文档得分如何结合查询匹配得分, 值"sum" 表示将关键词匹配得分与自定义函数得分相加
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQueryBuilder, filterFunctionBuilders)
                    .scoreMode(FunctionScoreQuery.ScoreMode.SUM)
                    .boostMode(CombineFunction.SUM);

            searchSourceBuilder.query(functionScoreQueryBuilder);
        }

        // 高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder().field(NoteIndex.FIELD_NOTE_TITLE)
                .preTags("<span style='color:red'>")
                .postTags("</span>");
        searchSourceBuilder.highlighter(highlightBuilder);

        // 分页
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(10);

        // 组装
        searchRequest.source(searchSourceBuilder);

        List<NoteSearchResponse>  noteSearchResponseList = new ArrayList<>();
        long totalHits =0;
        try {
            log.info("query note es  message :{}",searchRequest.toString());
            SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            totalHits = search.getHits().getTotalHits().value;
            SearchHits hits = search.getHits();
            for (SearchHit hit : hits) {

                Map<String, Object> sourceAsMap = hit.getSourceAsMap();

                NoteSearchResponse noteSearchResponse = new NoteSearchResponse();
                noteSearchResponse.setId(sourceAsMap.get(NoteIndex.FIELD_NOTE_ID).toString());
                noteSearchResponse.setType((Integer) sourceAsMap.get(NoteIndex.FIELD_TYPE));
                noteSearchResponse.setCover(sourceAsMap.get(NoteIndex.FIELD_NOTE_COVER).toString());
                noteSearchResponse.setVideoUri(sourceAsMap.get(NoteIndex.FIELD_NOTE_VIDEO_URI).toString());
                noteSearchResponse.setTitle(sourceAsMap.get(NoteIndex.FIELD_NOTE_TITLE).toString());

                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                if (!CollectionUtils.isEmpty(highlightFields) && highlightFields.containsKey(NoteIndex.FIELD_NOTE_TITLE)) {
                    noteSearchResponse.setHighlightTitle(highlightFields.get(NoteIndex.FIELD_NOTE_TITLE).toString());
                }
                noteSearchResponse.setAvatar(sourceAsMap.get(NoteIndex.FIELD_NOTE_CREATOR_AVATAR).toString());
                noteSearchResponse.setName(sourceAsMap.get(NoteIndex.FIELD_NOTE_CREATOR_NAME).toString());
                noteSearchResponse.setCreatorId(Long.valueOf((Integer)sourceAsMap.get(NoteIndex.FIELD_NOTE_CREATOR_ID)));
                noteSearchResponse.setUpdateTime(sourceAsMap.get(NoteIndex.FIELD_NOTE_UPDATE_TIME).toString());
                noteSearchResponse.setCommentTotal(NumberUtils.formatNumberString((Integer)sourceAsMap.get(NoteIndex.FIELD_NOTE_COMMENT_TOTAL)));
                noteSearchResponse.setCollectTotal(NumberUtils.formatNumberString((Integer)sourceAsMap.get(NoteIndex.FIELD_NOTE_COLLECT_TOTAL)));
                noteSearchResponse.setLikeTotal(NumberUtils.formatNumberString((Integer)sourceAsMap.get(NoteIndex.FIELD_NOTE_LIKE_TOTAL)));

                noteSearchResponseList.add(noteSearchResponse);
            }
        }catch (Exception e) {
            log.error("NoteSearchServiceImpl Exception", e);
        }

        return PageResponse.success(noteSearchResponseList,totalHits,noteSearchReq.getPage());
    }
}
